# -*- coding: utf-8 -*-
# @Time    : 2024/7/15 16:50
# @Author  : yujiahao
# @File    : 30_fastapi_dependency_yield.py
# @description:使用yield的依赖项


'''
在 FastAPI 中，yield 关键字用于依赖项函数中，但需要用 yield 代替 return 来达到这一目的，以便优雅地管理资源的生命周期。

    它的主要作用包括：
    - 创建资源：在请求处理期间创建所需的资源。
    - 返回资源：使用 yield 将资源返回给路径操作函数，使其在请求处理期间可用。
    - 清理资源：在请求处理完成后自动清理资源，确保资源被正确关闭或释放。

    数据库和文件句柄

【重点】

    - yield 之后抛出异常并不会被异常捕捉程序处理，所以还是返回正常的响应内容
    - 只有在 yield 之前抛出异常，异常捕捉程序才能处理成功，并返回报错响应给客户端

'''

from fastapi import Depends, FastAPI
from typing import TextIO

# 解题思路：
# 1. 创建一个 FastAPI 应用实例：首先，我们需要一个 FastAPI 应用实例来处理 HTTP 请求和定义路由。
# 2. 模拟数据和资源：为了演示，我们创建了一个假数据列表和模拟数据库连接类。
# 3. 定义依赖项：使用 `yield` 定义依赖项函数，用于管理资源的创建和清理。
# 4. 路径操作函数：定义处理特定路径的路径操作函数，这些函数依赖于我们定义的依赖项。

# 关于 yield 的详细用法：
# 在 Python 中，`yield` 关键字用于定义生成器函数。生成器函数在执行过程中可以暂停并返回一个值，
# 然后在需要时可以从暂停的地方继续执行。`yield` 通常用于迭代器，但在 FastAPI 中，我们可以利用它来管理资源的生命周期。
# 具体来说，使用 `yield` 可以在依赖项函数中创建资源，并在请求处理完成后清理资源。
# 这种方式确保资源在请求处理期间可用，并且在请求结束后被正确关闭或释放。
# 例如，我们可以使用 `yield` 打开一个数据库连接或文件，并在请求处理完成后关闭连接或文件。
# 这样可以确保资源在整个请求过程中是可用的，并且在请求结束后不会泄漏资源。

# 创建 FastAPI 应用实例
app = FastAPI()

# 假数据列表，用于模拟数据库中的数据
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]

# 声明一个类，将这个类当做依赖参数传递进去
class CommonQueryParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit

# 定义路径操作函数，依赖于 CommonQueryParams 类
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    """
    处理 /items/ 路径的 GET 请求。
    依赖于 CommonQueryParams 类，通过 Depends 注入实例。
    根据查询参数从假数据列表中提取相应的项目，并返回包含查询参数和项目列表的响应。
    """
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip: commons.skip + commons.limit]
    response.update({"items": items})
    return response

# 模拟数据库连接的类
class Database:
    def __init__(self, url: str):
        self.url = url
        self.connection = self.connect()

    def connect(self):
        # 模拟数据库连接
        return f"Connected to {self.url}"

    def close(self):
        # 模拟关闭数据库连接
        self.connection = None

# 定义依赖项函数，通过 yield 返回 Database 实例，并在请求处理完成后清理资源
def get_database():
    """
    定义了一个依赖项函数，通过 yield 返回 Database 实例。
    在 yield 之前创建 Database 实例 db，在 yield 之后使用 finally 块确保资源被正确清理。
    """
    db = Database("database_url")
    try:
        yield db  # 将资源返回给路径操作函数
    finally:
        db.close()  # 清理资源

# 使用依赖项的路径操作函数
@app.get("/items_db/")
async def read_items_db(db: Database = Depends(get_database)):
    """
    处理 /items_db/ 路径的 GET 请求。
    依赖于 get_database 函数，通过 Depends 注入 Database 实例。
    返回数据库连接状态。
    """
    return {"db_connection": db.connection}

# 定义依赖项函数，通过 yield 返回文件句柄，并在请求处理完成后关闭文件
def get_file():
    """
    定义了一个依赖项函数，通过 yield 返回文件句柄。
    在 yield 之前打开文件，在 yield 之后使用 finally 块确保文件被正确关闭。
    """
    file = open("somefile.txt", "r")
    try:
        yield file
    finally:
        file.close()

# 使用依赖项的路径操作函数，读取文件内容
@app.get("/readfile/")
async def read_file(file: TextIO = Depends(get_file)):
    """
    处理 /readfile/ 路径的 GET 请求。
    依赖于 get_file 函数，通过 Depends 注入文件句柄。
    读取文件内容并返回。
    """
    content = file.read()
    return {"content": content}

"""
详细说明：

1. CommonQueryParams 类：
    - 用于封装常见的查询参数，包括 q（查询字符串）、skip（跳过的记录数）和 limit（返回的最大记录数）。
    - 构造函数将这些参数初始化为实例变量。

2. read_items 函数：
    - 路径操作函数，用于处理 /items/ 路径的 GET 请求。
    - 依赖于 CommonQueryParams 类，通过 Depends 注入实例。
    - 根据查询参数从假数据列表中提取相应的项目，并返回包含查询参数和项目列表的响应。

3. Database 类：
    - 模拟数据库连接的类，包含 connect 和 close 方法。
    - connect 方法模拟建立数据库连接，close 方法模拟关闭数据库连接。

4. get_database 函数：
    - 定义了一个依赖项函数，通过 yield 返回 Database 实例。
    - 在 yield 之前创建 Database 实例 db，在 yield 之后使用 finally 块确保资源被正确清理。

5. read_items_db 函数：
    - 路径操作函数，用于处理 /items_db/ 路径的 GET 请求。
    - 依赖于 get_database 函数，通过 Depends 注入 Database 实例。
    - 返回数据库连接状态。

6. get_file 函数：
    - 定义了一个依赖项函数，通过 yield 返回文件句柄。
    - 在 yield 之前打开文件，在 yield 之后使用 finally 块确保文件被正确关闭。

7. read_file 函数：
    - 路径操作函数，用于处理 /readfile/ 路径的 GET 请求。
    - 依赖于 get_file 函数，通过 Depends 注入文件句柄。
    - 读取文件内容并返回。

这些示例展示了如何使用 yield 管理资源的生命周期，确保资源在请求处理完成后被正确清理。这种方式特别适用于需要显式创建和清理资源的场景，如数据库连接、文件句柄等。
"""
